using System;
using System.Collections;
using System.Threading;

namespace Decav.Collections
{

    /// <summary>
    /// A Queue derivation that blocks on a call to Dequeue if there are no items currently waiting in the Queue.
    /// </summary>
    [Serializable]
    public class BlockingQueue : Queue
    {

        /// <summary>
        /// A synchronization primitive that controls blocked access to the queue.
        /// </summary>
        private ManualResetEvent _sync;

        /// <summary>
        /// A timeout value to wait for something to be enqueued.
        /// </summary>
        private TimeSpan _dequeueTimeout;

        /// <summary>
        /// Creates a new BlockingQueue with an infinite timeout value.
        /// </summary>
        public BlockingQueue( )
            : this( TimeSpan.FromMilliseconds(Timeout.Infinite) )
        {
        }

        /// <summary>
        /// Creates a new BlockingQueue with the specified timeout value.
        /// </summary>
        /// <param name="timeout">The duration to wait for an item, or Timeout.Infinite.</param>
        public BlockingQueue( TimeSpan timeout )
            : base( )
        {

            if ( timeout.TotalMilliseconds < -1 )
                throw new ArgumentOutOfRangeException( "timeout", timeout, "The specified timeout value is invalid." );

            _dequeueTimeout = timeout;
            _sync =  new ManualResetEvent( false );

        }

        /// <summary>
        /// A timeout value to wait for something to be enqueued.
        /// </summary>
        /// <remarks>
        /// The default value (if not specified in the constructor) is -1 which is infinite. If an object is not
        /// enqueued before this timeout period, null will be returned from the Dequeue method.
        /// </remarks>
        public virtual TimeSpan DequeueTimeout
        {
            get
            {
                return _dequeueTimeout;
            }
        }

        /// <summary>
        /// Removes and returns the object at the beginning of the <see cref="T:System.Collections.Queue"></see>.
        /// </summary>
        /// <remarks>
        /// This implementation will block when there are no items waiting in the queue until the defined <see cref="DequeueTimeout"/>.
        /// If no items are added and the timeout expires, a null reference is returned.
        /// </remarks>
        /// <returns>
        /// The object that is removed from the beginning of the <see cref="T:System.Collections.Queue"></see>.
        /// </returns>
        public override object Dequeue( )
        {

            // Wait for an item to be added
            if ( this._sync.WaitOne( DequeueTimeout, false ) )
            {

                lock ( this.SyncRoot )
                {

                    // Reset the wait handle because we're about
                    // to pull the last item from the queue.
                    if ( this.Count <= 1 )
                        this._sync.Reset( );

                    // Get the next item from the queue
                    return base.Dequeue( );

                }   // lock

            }   // if
            else
            {

                return null;

            }   // else

        }

        /// <summary>
        /// Adds an object to the end of the <see cref="T:System.Collections.Queue"></see>.
        /// </summary>
        /// <remarks>
        /// This implementation will wake any threads that are waiting on new data to be added to the queue.
        /// </remarks>
        /// <param name="obj">The object to add to the <see cref="T:System.Collections.Queue"></see>. The value can be null.</param>
        public override void Enqueue( object obj )
        {

            lock ( this.SyncRoot )
            {

                // Add it to the base queue
                base.Enqueue( obj );
                this._sync.Set( );

            }   // lock

        }

    }   // class

}   // namespace
